package com.kevin.controller.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.kevin.common.api.R;
import com.kevin.common.exception.BusinessException;
import com.kevin.common.util.tree.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * 统一异常处理类
 *
 * @author wh
 * @version 1.0
 * @date 2020-05-18 10:09:26
 */
@Slf4j
@RestControllerAdvice
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class ExceptionTranslator {

    /**
     * 错误信息-缺少请求体
     */
    static final String MISS_REQUIRE_BODY = "Required request body is missing";

    /**
     * 错误信息-Json解析错误
     */
    static final String JSON_PARSE_ERROR = "JSON parse error";

    /**
     * 未发现服务
     */
    static final String NO_PROVIDER_FIND = "No provider available from registry";

    /**
     * 系统业务异常
     *
     * @param ex 错误信息
     */
    @ExceptionHandler(BusinessException.class)
    public R<Object> businessException(BusinessException ex) {
        log.error("业务错误,{}", ex.getMessage());
        return R.failed(ex.getMessage());
    }

    /**
     * Spring校验方法参数错误 @Valid
     *
     * @param ex 错误信息
     */
    @ExceptionHandler({BindException.class})
    public R<Object> bindException(BindException ex) {
        return R.failed(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }

    /**
     * Spring校验方法参数错误 @Valid
     *
     * @param ex 错误信息
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public R<Object> bindMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        return R.failed(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }

    /**
     * Spring校验必填参数错误 通常都是@RequestParam
     *
     * @param ex 错误信息
     */
    @ExceptionHandler({MissingServletRequestParameterException.class})
    public R<Object> requestMissingServletRequest(MissingServletRequestParameterException ex) {
        return R.failed("缺少必要参数,参数名称为" + ex.getParameterName());
    }

    /**
     * Filter层的异常
     *
     * @param ex 异常信息
     */
    @ExceptionHandler(ServletException.class)
    public R<Object> handleServletException(ServletException ex) {
        Throwable cause = ex.getCause();
        if (cause instanceof BusinessException) {
            return businessException((BusinessException) cause);
        } else {
            log.error("Filter内异常:", ex);
            return R.failed(cause.getMessage());
        }
    }

    /**
     * 可以通过{@link Assert}方式进行断言
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public R<Object> handleIllegalArgumentException(IllegalArgumentException e) {
        log.error("参数异常:{}", e.getMessage());
        return R.failed(e.getMessage());
    }

    /**
     * 缺少参数 异常
     */
    @ExceptionHandler(MissingServletRequestPartException.class)
    public R<Object> handleMissingServletRequestPartException(MissingServletRequestPartException e) {
        log.error("参数异常:{}", e.getMessage());
        return R.failed("缺少参数:" + e.getRequestPartName());
    }

    /**
     * Spring校验必填参数错误 通常都是@RequestParam
     *
     * @param ex 错误信息
     */
    @ExceptionHandler({HttpMessageNotReadableException.class})
    public R<Object> httpMessageNotReadableException(HttpMessageNotReadableException ex) {
        if (Objects.requireNonNull(ex.getMessage()).contains(MISS_REQUIRE_BODY)) {
            return R.failed("POST请求缺少请求体!");
        }
        if (Objects.requireNonNull(ex.getMessage()).contains(JSON_PARSE_ERROR)) {
            return R.failed("Json格式错误," + StrUtil.maxLength(ex.getMessage(), 50));
        }
        return handleError(ex);
    }

    /**
     * 用户接口基本鉴权
     *
     * @param e
     * @return
     */
    @ResponseStatus(value = HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(value = NotLoginException.class)
    public R<?> handUserExceptionHandler(NotLoginException e) {
        SpringMVCUtil.getResponse().setContentType("application/json;charset=UTF-8");
        SaHolder.getResponse()
                // 允许指定域访问跨域资源
                .setHeader("Access-Control-Allow-Origin", "*")
                // 允许所有请求方式
                .setHeader("Access-Control-Allow-Methods", "*")
                // 有效时间
                .setHeader("Access-Control-Max-Age", "3600")
                // 允许的header参数
                .setHeader("Access-Control-Allow-Headers", "*");
//        e.printStackTrace();

        HttpServletRequest request = SpringMVCUtil.getRequest();
        log.info("auth failed {} token:{},ip:{}:uri:{}", e.getMessage(), request.getHeader("token"), ServletUtil.getClientIP(request), request.getRequestURI());
        // 判断场景值，定制化异常信息
        String message = "";
        if (e.getType().equals(NotLoginException.NOT_TOKEN)) {
            message = "未提供token";
        } else if (e.getType().equals(NotLoginException.INVALID_TOKEN)) {
            message = "token无效";
        } else if (e.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
            message = "token已过期";
        } else if (e.getType().equals(NotLoginException.BE_REPLACED)) {
            message = "token已被顶下线";
        } else if (e.getType().equals(NotLoginException.KICK_OUT)) {
            message = "token已被踢下线";
        } else {
            message = "当前会话未登录";
        }
        SpringMVCUtil.getResponse().setStatus(HttpStatus.UNAUTHORIZED.value());
        return R.failed(message);
    }

    /** 无权限异常 */
    @ExceptionHandler(value = NotPermissionException.class)
    public R<?> handUserExceptionHandler(NotPermissionException e) {
        return R.failed("无权操作");
    }


    /**
     * 未知错误处理
     *
     * @param ex 错误信息
     */
    @ExceptionHandler(Exception.class)
    public R<Object> handleError(Exception ex) {
        log.error("未知异常:", ex);
        HttpServletRequest request = RequestUtil.getRequest();


        // 如果开启则默认发送
        ThreadUtil.execute(() -> {
            try {
                // 标题
                String title;
                if (ex.getStackTrace() != null && ex.getStackTrace().length > 0) {
                    StackTraceElement stackTraceElement = ex.getStackTrace()[0];
                    title = String.format("文件:%s\n方法:%s\n行数:%s\n异常:%s\n信息:%s", stackTraceElement.getFileName(), stackTraceElement.getMethodName(), stackTraceElement.getLineNumber(), ex.getClass().getName(), ex.getMessage());
                } else {
                    // 错误信息
                    title = String.format("异常:%s\n信息:%s", ex.getClass().getName(), ex.getMessage());
                }
                // TODO:对接异常通知

                Object requestBody = request.getAttribute("body");

                log.info("============================异常日志纪录成功!===============================");
            } catch (Exception e) {
                log.error("============================日志记录失败,错误信息===============================", e);
            }
        });
        return R.failed();
    }
}
